#include "glad/glad.h"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include "hzpch.h"
#include "WindowsWindow.h"

#include "Hazel/Events/ApplicationEvent.h"
#include "Hazel/Events/Mouse.h"
#include "Hazel/Events/KeyEvent.h"

namespace Hazel {
    static bool s_GLFWInitialized = false;

    static void GLFWErrorCallback(int error_code, const char* description)
    {
        HZ_CORE_ERROR("GLFW Error {0} {1}", error_code, description);
    }

    Window* Window::Create(const WindowProps& props)
    {
        return new WindowsWindow(props);
    }

    WindowsWindow::WindowsWindow(const WindowProps& props)
    {
        Init(props);
    }

    WindowsWindow::~WindowsWindow()
    {
        Shutdown();
    }

    void WindowsWindow::OnUpdte()
    {
        glfwPollEvents();
        glfwSwapBuffers(m_Window);
    }

    void WindowsWindow::SetVSync(bool enabled)
    {
        if (enabled) 
        {
            glfwSwapInterval(1);
        }
        else
        {
            glfwSwapInterval(0);
        }

        m_Data.vSync = enabled;
    }

    bool WindowsWindow::IsVSync()
    {
        return m_Data.vSync;
    }

    void WindowsWindow::Init(const WindowProps& props)
    {
        m_Data.Title = props.Title;
        m_Data.Width = props.Width;
        m_Data.Height = props.Height;

        HZ_CORE_INFO("Create Window {0} {1} {2}", props.Title, props.Width, props.Height);

        if (!s_GLFWInitialized)
        {
            int success = glfwInit();
            HZ_CORE_ASSERT(success, "Could not initialize GLFW!");
            
            glfwSetErrorCallback(GLFWErrorCallback);

            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
            #ifdef __APPLE__
                glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);  //MAC OSX needs
            #endif
                glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
                glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
            s_GLFWInitialized = true;
        }

        m_Window = glfwCreateWindow(static_cast<int>(m_Data.Width), static_cast<int>(m_Data.Height), m_Data.Title.c_str(), nullptr, nullptr);
        if (m_Window == nullptr) {
            std::cout << "Failed to create GLFW window" << std::endl;
            glfwTerminate();
            HZ_CORE_ASSERT(status, "Failed to create GLFW window!");
        }
        glfwMakeContextCurrent(m_Window);
        int status = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
        HZ_CORE_ASSERT(status, "Failed to initialize Glad!");

        glfwSetWindowUserPointer(m_Window, &m_Data);
        SetVSync(true);

        // Set GLFW callbacks
        glfwSetWindowSizeCallback(m_Window, [](GLFWwindow* window, int width, int height) 
        {
            WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));
            data.Width = width;
            data.Height = height;

            WindowResizeEvent e{ data.Width, data.Height };
            data.EventCallback(e);
        });

        glfwSetWindowCloseCallback(m_Window, [](GLFWwindow* window)
        {
                WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));

                WindowCloseEvent e{};
                data.EventCallback(e);
        });

        glfwSetKeyCallback(m_Window, [](GLFWwindow* window, int key, int scancode, int action, int mods)
        {
            WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));

            switch (action)
            {
            case GLFW_PRESS:
            {
                KeyPressedEvent e{ key, 0 };
                data.EventCallback(e);
                break;
            }
            case GLFW_RELEASE:
            {
                KeyReleasedEvent e{ key };
                data.EventCallback(e);
                break;
            }
            case GLFW_REPEAT:
            {
                KeyPressedEvent e{ key, 1 };
                data.EventCallback(e);
                break;
            }
            default:
                break;
            }
            
        });

        glfwSetMouseButtonCallback(m_Window, [](GLFWwindow* window, int button, int action, int mods)
        {
            WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));

            switch (action)
            {
            case GLFW_PRESS:
            {
                MouseButtonPressedEvent e{ button };
                data.EventCallback(e);
                break;
            }
            case GLFW_RELEASE:
            {
                MouseButtonReleasedEvent e{ button };
                data.EventCallback(e);
                break;
            }
            default:
                break;
            }
        });

        glfwSetScrollCallback(m_Window, [](GLFWwindow* window, double xoffset, double yoffset)
        {
            WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));
            MouseScrolledEvent e{ (float)xoffset, (float)yoffset };
            data.EventCallback(e);
        });

        glfwSetCursorPosCallback(m_Window, [](GLFWwindow* window, double xpos, double ypos)
        {
            WindowData& data = *(static_cast<WindowData*>(glfwGetWindowUserPointer(window)));
            MouseMovedEvent e{ (float)xpos, (float)ypos };
            data.EventCallback(e);
        });
    }

    void WindowsWindow::Shutdown()
    {
        glfwDestroyWindow(m_Window);
    }
}